//////////////////////////////////////////////
//                                          //
//     JavaScript Container Objects         // 
//                                          //
//////////////////////////////////////////////

////////////////
//  Question  //
////////////////
function Question(loadString)
{
	this.correctAnswers = new Array();
	this.answer = null;
	
	this.setCorrectAnswers = QuestionSetCorrectAnswers;
	this.setAnswer = QuestionSetAnswer;
	this.isCorrect = QuestionIsCorrect;
	this.dump = QuestionDump;
	this.load = QuestionLoad;
	this.toString = QuestionToString;
	
	if (Question.arguments.length > 0)
	{
		this.load(loadString);
	}
}

function QuestionToString()
{
	return this.dump();
}

function QuestionDump()
{
	var temp = this.answer;
	for (var i=0;i<this.correctAnswers.length;i++)
	{
		temp += ("," + this.correctAnswers[i]);
	}
	
	return temp;
}

function QuestionLoad(loadString)
{
	var index = null;
	var counter = 0;
	loadString += ",";
	
	// First load answer.
	index = loadString.indexOf(",");
	if (index == -1)
	{
		alert("Error loading Question object in containers.js");
		return;
	}
	this.answer = loadString.slice(0,index);
	loadString = loadString.slice(index+1, loadString.length);
	
	// Next load correct answers.
	this.correctAnswers = new Array();
	while ((index = loadString.indexOf(",")) != -1)
	{
		this.correctAnswers[counter] = loadString.slice(0,index);
		loadString = loadString.slice(index+1, loadString.length);
		counter++;
	}
}

function QuestionSetAnswer(ans)
{
	if ( (ans + "").indexOf(",") != -1)
	{
		alert("Error in call to function Question.setAnswer(" + ans + ").  The parameter cannot contain commas.");
		return;
	}
	this.answer = ans + "";
}

function QuestionSetCorrectAnswers()
{
	for (var i=0;i<QuestionSetCorrectAnswers.arguments.length;i++)
	{
		if ((QuestionSetCorrectAnswers.arguments[i]+"").indexOf(",") != -1)
		{
			alert("Error in call to function Question.setCorrectAnswers(" + QuestionSetCorrectAnswers.arguments.toString() + ").  The parameters cannot contain commas.");
			return;
		}
	}

	this.correctAnswers = new Array();
	for (var i=0;i<QuestionSetCorrectAnswers.arguments.length;i++)
	{
		this.correctAnswers[i] = QuestionSetCorrectAnswers.arguments[i] + "";
	}
}

function QuestionIsCorrect()
{
	if (this.answer == null)
	{
		return false;
	}
	for (var i=0;i<this.correctAnswers.length;i++)
	{
		if (this.answer == this.correctAnswers[i])
		{
			return true;
		}
	}
	return false;
}

///////////////
// Position  //
///////////////
function Position(X,Y)
{
	if (typeof X != "number" || typeof Y != "number" || Position.arguments.length != 2)
	{
		alert("The Position Object must be passed 2 numbers as parameters.  Object not created.");
		return null;
	}

	this.x = X;
	this.y = Y;
	this.getX = PositionGetX;
	this.getY = PositionGetY;
	this.setX = PositionSetX;
	this.setY = PositionSetY;
	this.distance = PositionDistance;
	this.slope = PositionSlope;
}

function PositionSlope(posRef)
{
	return (this.y-posRef.getY())/(this.x-posRef.getX());
}

function PositionGetX()
{
	return this.x;
}

function PositionGetY()
{
	return this.y;
}

function PositionSetX(X)
{
	this.x = X;
}

function PositionSetY(Y)
{
	this.y = Y;
}

function PositionDistance(posRef)
{
	return Math.sqrt( Math.pow((this.x - posRef.x),2) + Math.pow((this.y - posRef.y),2) );
}

///////////////
// Dimension //
///////////////
function Dimension(w,h)
{
	if (typeof w != "number" || typeof h != "number" || Dimension.arguments.length != 2)
	{
		alert("The Dimension Object must be passed 2 numbers as parameters.  Object not created.");
		return null;
	}

	this.width = w;
	this.height = h;
	this.setWidth = DimensionSetWidth;
	this.setHeight = DimensionSetHeight;
	this.getWidth = DimensionGetWidth;
	this.getHeight = DimensionGetHeight;
}

function DimensionSetHeight(h)
{
	this.height = h;
}

function DimensionSetWidth(w)
{
	this.width = w;
}

function DimensionGetHeight(h)
{
	return this.height;
}

function DimensionGetWidth(w)
{
	return this.width;
}

///////////////////
//     Stack     //
///////////////////
function Stack() 
{ 
	this.stackArray = new Array();
	this.pop = stackPop;
	this.push = stackPush;
	this.getLength = stackLength;
	this.top = -1;
	this.list = stackList;
	this.toString = StackToString;
	this.clear = StackClear;
} 
 
function StackClear()
{
	this.stackArray = new Array();
	this.top = -1;
} 

function stackLength() 
{
	return this.top + 1;
}

function stackList() 
{
	var temp = "Top --> Bottom:  ";
	
	for ( var i=this.top; i>=0; i-- ) 
	{
   	temp += (this.stackArray[i]);
   	if ( i != 0 ) 
   	{
   		temp += ",";
   	}
	}

	alert(temp);
}

function StackToString()
{
	var arrVal = new Array();
	
	for (var i=0;i<=this.top;i++)
	{
		arrVal[i] = this.stackArray[i].toString();
	}
	
	return arrVal.join(",");
}

function stackPop()
{
	if (this.top < 0) return null;
	else 
	{
		return this.stackArray[this.top--];
	}
}

function stackPush(obj)
{
	this.stackArray[++this.top] = obj;
}

/////////////////////
//      Queue      //
/////////////////////
function Queue() 
{
	this.head = null;
	this.tail = null;
	this.getLength = queueLength;
	this.enqueue = QueueEnqueue;
	this.dequeue = QueueDequeue;
	this.remove  = QueueRemoveLast;
	this.removeFirst  = QueueRemoveFirst;
	this.removeLast  = QueueRemoveLast;
	this.list = queueList;
	this.top = QueueTop;
}

function queueList() 
{
	var temp = "Head --> Tail:  ";
	var next = this.head;
	
	while ( next != null ) 
	{
   	temp += next.obj;;
   	if ( next.next != null ) 
   	{
   		temp += ",";
   	}
   	
   	next = next.next;
	}

	alert(temp);
}


function QueueNode( value ) 
{
	this.next = null;
	this.prev = null;
	this.obj = value;
}

function QueueEnqueue(value) 
{
	var temp;
	temp = new QueueNode(value);
	
	if ( this.tail == null ) 
	{
		this.tail = temp;
	}
	
	temp.next = this.head;
	
	if (this.head != null) 
		this.head.prev = temp;
		
	this.head = temp;
}

function QueueDequeue() 
{
	var temp;
	
	if ( this.tail == null ) 
	{
		return null;
	}
	else
	{
		temp = this.tail;
		if ( temp.prev != null ) 
		{
   			temp.prev.next = null;
		}
		else
		{
			this.head = null;
		}
		this.tail = temp.prev;
		return temp.obj;
	}
}

function QueueTop() 
{
	if ( this.tail == null ) 
	{
		return null;
	}
	else
	{
		return this.tail.obj;
	}
}

function QueueRemoveLast(value) 
{
	var next = this.head;
	while ( next != null ) 
	{
   	if ( next.obj == value ) 
   	{
   		if ( next.next != null ) 
   		{	
   			next.next.prev = next.prev;
   		}
   		else
   		{
   			this.tail = next.prev;
   		}
   		
   		if ( next.prev != null ) 
   		{	
   			next.prev.next = next.next;
   		}
   		else
   		{
   			this.head = next.next;
   		}
   		return next.obj;
		}
		next = next.next;
	}
	return null;
}

function QueueRemoveFirst(value) 
{
	var next = this.tail;
	while ( next != null ) 
	{
   	if ( next.obj == value ) 
   	{
   		if ( next.next != null ) 
   		{	
   			next.next.prev = next.prev;
   		}
   		else
   		{
   			this.tail = next.prev;
   		}
   		
   		if ( next.prev != null ) 
   		{	
   			next.prev.next = next.next;
   		}
   		else
   		{
   			this.head = next.next;
   		}
   		
   		return next.obj;
		}
		next = next.prev;
	}
	return null;
} 

function queueLength() 
{
	var next = this.head;
	var count = 0;
	
	while ( next != null ) 
	{
   	count++;
   	next = next.next;
	}
	return count;
}

////////////////
// Hash Table //
////////////////

function HashTable(spineLength, hashFunc)
{
	this.length = 0;
	if (hashFunc == null) this.hashFunction = VarStoreHash;
	else this.hashFunction = hashFunc;
	this.spine = new Array();
	for (var i=0;i<spineLength;i++)
	{
		this.spine[i] = null;
	}
	this.put = HashTableInsert;
	this.get = HashTableRetrieve;
	this.remove = HashTableRemove;
	this.getLength = HashTableGetLength;
	this.clear = HashTableClear;
	this.toString = HashTableToString;
	this.toHTML = HashTableToHTML;
	this.getKeys = HashTableGetKeys;
	
	return this;
}

function HashTableGetKeys()
{
	var keyArray = new Array();
	for (var i=0;i<this.spine.length;i++)
	{
		var ptr = this.spine[i];
		while (ptr != null)
		{
			keyArray[keyArray.length] = ptr.token;
			ptr = ptr.next;
		}
	}
	return keyArray;
}

function HashTableClear()
{
	for (var i=0;i<this.spine.length;i++)
	{
		var ptr = this.spine[i];
		var prev = ptr;
		// This first loop sets prev
		// to point to the last node
		// in the linked list (null if
		// no nodes present).
		while (ptr != null)
		{
			prev = ptr;
			ptr = ptr.next;
		}
		ptr = prev;
		prev = null;
		// Back up through list, breaking
		// links as we go.
		if (ptr != null)
		{
			while (ptr.prev != null)
			{
				ptr = ptr.prev;
				ptr.next = null;
				this.length--;
			}
			// Delete node that may have been
			// on the spine.
			this.spine[i] = null;
			this.length--;
		}
	}
	return true;
}

function HashTableToString()
{
	var temp = "length: " + this.length + "\n";
	for (var i=0;i<this.spine.length;i++)
	{
		var temp2 = i + ":";
		var ptr = this.spine[i];
		if (ptr != null)
		{
			while (ptr != null)
			{
				temp2 += "[" + ptr.token + "," + ptr.value + "]->"
				ptr = ptr.next;
			}
			temp2 = temp2.slice(0,temp2.length-2);
		}
		else
		{
			temp2 += "[null]";
		}
		if (i < this.spine.length - 1)
			temp += temp2 + "\n";
		else
			temp += temp2
	}
	return temp;
}

function HashTableToHTML()
{
	var temp = "<table>";
	temp += "<tr><td NOWRAP>length: " + this.length + "</td></tr>";
	for (var i=0;i<this.spine.length;i++)
	{
		var temp2 = "<tr><td NOWRAP><b>" + i + "</b>:";
		var ptr = this.spine[i];
		if (ptr != null)
		{
			while (ptr != null)
			{
				temp2 += "[" + ptr.token + "," + ptr.value + "]->"
				ptr = ptr.next;
			}
			temp2 = temp2.slice(0,temp2.length-2);
		}
		else
		{
			temp2 += "[null]";
		}
		if (i < this.spine.length - 1)
			temp += temp2 + "</td></tr>";
		else
			temp += temp2
	}
	temp += "</table>";
	return temp;
}

function HashTableGetLength()
{
	return this.length;
}

function HashTableRemove(tok)
{
	var row = this.hashFunction(tok);
	var ptr = this.spine[row];
	while (ptr != null)
	{
		if (ptr.token == tok)
		{
			if (ptr.prev != null)
			{
				ptr.prev.next = ptr.next;
			}
			else
			{
				this.spine[row] = ptr.next;
			}
			if (ptr.next != null)
			{
				ptr.next.prev = ptr.prev;
			}
			ptr = null;
			this.length--;
			return true;
		}
		ptr = ptr.next;
	}
	return false;
}

function HashTableRetrieve(tok)
{
	var row = this.hashFunction(tok);
	var ptr = this.spine[row];
	
	while (ptr != null)
	{
		if (ptr.token == tok)
		{
			return ptr.value;
		}
		ptr = ptr.next;
	}
	return null;
}

function HashTableInsert(tok, val)
{
	var row = this.hashFunction(tok);
	var newNode = new HashNode(tok, val);
	var prev, ptr;
	prev = ptr = this.spine[row];
	
	//alert("row: " + row);
	
	while (ptr != null)
	{
		if (ptr.token == tok)
		{
			break;
		}
		else
		{
			prev = ptr;
			ptr = ptr.next;
		}
	}
	
	// At this point if ptr == null, then the token
	// was not found in the row.  If ptr != null, then
	// the token was found.
	if (ptr == null)
	// token not found in row.
	{//alert("token not found");
		if (prev == null)
		// Row must be empty, so link to spine.
		{
			this.spine[row] = newNode;
			//alert("adding node to this.spine[0]");
		}
		else
		// Row is not empty, so link the new node
		// in at the end of the list.
		{
			//alert("adding node at end of list");
			prev.next = newNode;
			newNode.prev = prev;
		}
		this.length++;
	}
	else
	// token found in row, so change value of node.
	{	//alert("token found, changing it's value");
		ptr.value = val;
	}
	return true;
}

function HashNode(tok, val)
{
	this.value = val;
	this.token = tok;
	this.next = null;
	this.prev = null;
	
	return this;
}

///////////////
//  VarStore //
///////////////
///////////////////////////////////////////////////////////
// Q: What is a VarStore?                                //
//                                                       //
// A: A VarStore is an object that you can use to store  //
//    name, value pairs.  It is a lot like creating a    //
//    JavaScript variable, except the values you save in //
//    your VarStore object have to be strings.           //
//                                                       //
//    The main motivation for having a VarStore object   //
//    is its ability to dump all of its contents into    //
//    a single string.  I know that doesn't sound all    //
//    that exciting at first, but just hang on there     //
//    cowboy.  You can take the string representation    //
//    of your VarStore and save it as a single cookie,   //
//    or store it in a database as a single value.       //
//    Believe me, this comes in handy.  What?  Still     //
//    not convinced?  Maybe I'll try an example.         //
//                                                       //
//    Suppose you have to save BeenTheres for a whole    //
//    bunch of pages, and those BeenTheres need to       //
//    exist between multiple user sessions in the        //
//    courseware.  You will need to save the             //
//    BeenTheres somewhere so the courseware can recall  //
//    them later.  You could save the values in a        //
//    database via ASP pages.  The BeenTheres could be   //
//    stored as individual records in the database,      //
//    requiring code to manage the numerous number       //
//    of entries in the database, and the many           //
//    individual calls for setting and getting the       //
//    BeenThere values.                                  //
//                                                       //
//    Alternatively, you could manage all your           //
//    BeenTheres in a VarStore, and when it comes time   //
//    to pass the values to the server to be saved,      //
//    you can dump the contents of the VarStore into     //
//    a single string value.  Now all you have to do     //
//    to save all of your BeenTheres is save this single //
//    string to the database.  This GREATLY simplifies   //
//    the ASP code, as it will now only have to manage   //
//    passing the value of a single record in the        //
//    database back and forth between server and client. //
///////////////////////////////////////////////////////////
//                                                       //
// Create: 			myVarStore = new VarStore();         //
// Set Variable:	myVarStore.setValue("bt0","true");   //
// Get Value:		myVarStore.getValue("bt0");          //
// Clear Out Store:	myVarStore.clear();                  //
// Remove Variable:	myVarStore.remove("bt0");            //
// Variable Exists:	myVarStore.tokenExists("bt0");       //
// Load from String:myVarStore.load(someString);         //
// Dump to String:	myVarStore.dump();                   //
//                                                       //
///////////////////////////////////////////////////////////
var VARSTORE_SEPARATOR_1 = "*";
var VARSTORE_SEPARATOR_2 = "|";
var VAR_STORE_HASH_LENGTH = 1000;
 
function VarStore()
{ 
	//this.tokenArray = new Array();
	this.hashTable = new HashTable(VAR_STORE_HASH_LENGTH, VarStoreHash);
	this.getLength = VarStoreGetLength;
	this.clear = VarStoreClear;
	this.dump = VarStoreDump;
	this.load = VarStoreLoad;
	this.remove = VarStoreRemove;
	this.getValue = VarStoreGet;
	this.setValue = VarStoreSetValue;
	this.tokenExists = VarStoreTokenExists;
	this.hash = VarStoreHash;
	this.toString = VarStoreToString;
	this.dirty = false;
	this.toHTML = VarStoreToHTML;
	
	return this;
}

function VarStoreToHTML()
{
	return this.hashTable.toHTML();
}

function VarStoreGet(tok)
{
	tok = tok + "";
	return this.hashTable.get(tok);
}

function VarStoreRemove(tok)
{
	tok = tok + "";
	
	if (this.hashTable.remove(tok))
	{
		this.dirty = true;
		return true;
	}
	return false;
}

function VarStoreClear()
{
	if (this.hashTable.clear())
	{
		this.dirty = true;
		return true;
	}
	return false;
}

function VarStoreGetLength()
{
	return this.hashTable.getLength();
}

function VarStoreHash(strTok)
{
	strTok = strTok + "";
	var temp = 0;
	for (var i = 0;i<strTok.length && i<16;i++)
	{
		temp += (strTok.charCodeAt(i) * (i+1));
	}
	return temp % VAR_STORE_HASH_LENGTH;
}

function VarStoreDump()
{
	var str = "";
	var count = 0;
	
	for (var i=0;i<this.hashTable.spine.length;i++)
	{
		var ptr = this.hashTable.spine[i];
		while (ptr != null)
		{
			str += ((count==0)?"":VARSTORE_SEPARATOR_1) + ptr.token + VARSTORE_SEPARATOR_2 + ptr.value;
			ptr = ptr.next;
			count++;
		}
	}	
	this.dirty = false;
	return str;
}

function VarStoreLoad(str)
{
	var pair = "";
	var separatorPosition = -1;
	
	//Strip token|value pairs off str one at a time.
	while ( (separatorPosition = str.indexOf(VARSTORE_SEPARATOR_1)) != -1 )
	{
		//temp <- next pair
		pair = str.slice(0,separatorPosition);
		//str <- (str - next pair)
		str = str.slice(separatorPosition+1,str.length);
		
		//Now that we have isolated a token|value pair
		//strip out the token and value, and add them
		//to this objects storage.
		separatorPosition = pair.indexOf(VARSTORE_SEPARATOR_2);
		if (separatorPosition == -1)
		{
			alert("Error in call to VarStore::load()\n"+str+" is an invalid token" + VARSTORE_SEPARATOR_2 + "value pair. VarStore will be cleared.");
			this.clear();
			return false;
		}
		this.setValue( pair.slice(0,separatorPosition), pair.slice(separatorPosition+1,pair.length) );
	}
	
	//At this point we have at most one pair left,
	//and at least none.
	if (str != "")
	{
		separatorPosition = str.indexOf(VARSTORE_SEPARATOR_2);
		if (separatorPosition == -1)
		{
			alert("Error in call to VarStore::load()\n"+str+" is an invalid token" + VARSTORE_SEPARATOR_2 + "value pair. VarStore will be cleared.");
			this.clear();
			return false;
		}
		this.setValue( str.slice(0,separatorPosition), str.slice(separatorPosition+1,str.length) );
	}
	
	//If processing gets this far, then load was successful.
	this.dirty = false;
	return true;
}

function VarStoreSetValue(tok,val)
{
	//Check for reserved characters
	if ( ((tok+"").indexOf(VARSTORE_SEPARATOR_1) != -1) || ((tok+"").indexOf(VARSTORE_SEPARATOR_2) != -1) || ((val+"").indexOf(VARSTORE_SEPARATOR_1) != -1) || ((val+"").indexOf(VARSTORE_SEPARATOR_2) != -1) )
	{
		alert("Error in call to VarStore::setValue()\n'" + VARSTORE_SEPARATOR_1 + "' and '" + VARSTORE_SEPARATOR_2 + "' are reserved characters and cannot be used as a token name or value.\n\ntoken: " + tok + "\nvalue: " + val);
		return false;
	}
	if (this.hashTable.put(tok + "", val))
	{
		this.dirty = true;
		return true;
	}
	return false;
}

function VarStoreTokenExists(tok)
{
	tok = tok + "";
	return (this.hashTable.get(tok) != null);
}

function VarStoreToString()
{
	return this.hashTable.toString();
}

////////////////////////////
// Array helper functions //
////////////////////////////
function removeArrayElement(ar,index)
{
	if (index < 0)
	{
		alert("Error in call to removeArrayElement()\nThe supplied index is less than 0: " + index + ".");
		return false;
	}

	for (var i=index;i<ar.length-1;i++)
	{
		ar[i] = ar[i+1];
	}
	
	if (index < ar.length)
	{
		ar.length--;
	}
	else
	{
		alert("Error in call to removeArrayElement()\nThe supplied index exceeds the dimension of the array:\nindex=" + index + "\narray length=" + ar.length);
		return false;
	}
	
	return true;
}

/////////////////
// StringStack //
/////////////////
function StringStack()
{
	this.stack = new Stack();
	this.load = StringStackLoad;
	this.dump = StringStackDump;
	this.pop = function() {return this.stack.pop()};
	this.push = function(strVal) {this.stack.push(strVal.toString())};
	this.getLength = function() {return this.stack.getLength()};
	this.list = function() {this.stack.list()};
	this.clear = function() {this.stack.clear()};
}

function StringStackLoad(strVal)
{
	this.stack.clear();
	if (strVal.length > 0)
	{
		var strArray = strVal.split(",");
		
		for (var i=0;i<strArray.length;i++)
		{
			this.stack.push(strArray[i]);
		}
	}
}

function StringStackDump()
{
	return this.stack.toString();
}

